﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Wintellect.PowerCollections;

namespace RegexStringMatcher {

    /// <summary>
    /// Concurrent batch string batch against a list of regular expressions helper class.
    /// It puts together the following features:
    /// <ul>
    /// <li>The string to be matched,</li>
    /// <li>An array of precompiled regular expressions,</li>
    /// <li>An instance of waitlock to orchestrate a collection of instances of this class,</li>
    /// <li>A method that runs a match against the regular expressions in concurrent manner.</li>
    /// </ul>
    /// Although most of the class is put together from immutable components in immutable manner, 
    /// it does have a state, hence one instance of this class should be ran in a separate thread.
    /// </summary>
    public class RegexStringMatcher {

        private static int numberNotDone; // workaround against limitation of 64 waithandles max.

        private readonly string m_str;

        private readonly ManualResetEvent doneEvent;

        private readonly Regex[] regexes;

        private bool m_isMatch;

        public String str {
            get {
                return m_str;
            }
        }
        /// <summary>
        /// Result of the matching, true if there is a match against any of the regexes passed to the constructor.
        /// </summary>
        public bool IsMatch {
            get {
                return m_isMatch;
            }
        }

        /// <summary>
        /// Constructor with all things the class needs.
        /// </summary>
        /// <param name="inputString">The string to match.</param>
        /// <param name="regexes">Regex instances to match against.</param>
        /// <param name="doneEvent">The wait lock.</param>
        public RegexStringMatcher(string inputString, Regex[] regexes, ManualResetEvent doneEvent) {
            this.regexes = regexes;
            this.m_str = inputString;
            this.doneEvent = doneEvent;
            Interlocked.Increment(ref numberNotDone);
        }

        /// <summary>
        /// Runs actual matching optimistically: if one string matches against any of the regexes, 
        /// sets the state to "matched" and exits with marking the wait lock to "Done".
        /// </summary>
        public void exec() {
            foreach(Regex regex in regexes) {
                if (regex.IsMatch(m_str)) {
                    m_isMatch = true;
                    break;
                }
            }
            if (Interlocked.Decrement(ref numberNotDone) == 0) {
                doneEvent.Set();
            }
        }
    }

    /// <summary>
    /// Concurrent string matcher.
    /// </summary>
    public class BulkStringMatcher {

        private int expectedPercentageOfMatchedStrings = 1;

        /// <summary>
        /// This value is used by the matcher to tune up return value initial size, best guess is good enough.
        /// Making a mistake could result either in a real size of the returned list bigger than needed or 
        /// too many reallocations as the list grows.
        /// </summary>
        public int expectedMatchPercent {
            set {
                this.expectedPercentageOfMatchedStrings = value;
            }
            get {
                return this.expectedPercentageOfMatchedStrings;
            }
        }
        /// <summary>
        /// Tuning up the thread pool - specify the guestimate of the number of phycical CPUs/cores on the machine.
        /// </summary>
        public int MaxThreads {
            set {
                ThreadPool.SetMaxThreads(value, 1);
            }
        }

        /// <summary>
        /// Gives a client an option of weeding out duplicate strings.
        /// </summary>
        /// <param name="originalStrings">An array of strings with possible duplicates.</param>
        /// <returns>Unique strings out of the argument.</returns>
        public IList<string> GetDistinctStrings(string[] originalStrings) {
            return new List<string>(new Set<string>(originalStrings));
        }

        /// <summary>
        /// Runs concurrent string matching with the given strings against given filters.
        /// </summary>
        /// <param name="inputStrings">The strings to match.</param>
        /// <param name="inputFilters">The regex expressions to match against.</param>
        /// <returns>The list of strings that matched against any of the filters.</returns>
        /// <exception cref="ArgumentException">Thrown if either the string array is empty or the input filter array is empty</exception>
        public List<string> GetMatches(string[] inputStrings, string[] inputFilters) {
            if (inputStrings.Length < 1 || inputFilters.Length < 1) {
                throw new ArgumentException(
                    String.Format("Not enough work; string count={0}, filter count={1}"
                    , inputStrings.Length, inputFilters.Length));
            }
            RegexStringMatcher[] stringMatchers = new RegexStringMatcher[inputStrings.Length];
            Regex[] regexes = new Regex[inputFilters.Length];
            for (int index = 0; index < inputFilters.Length; index++) {
                regexes[index] = new Regex(inputFilters[index], RegexOptions.Compiled);
            }

            ManualResetEvent doneEvent = new ManualResetEvent(false);
            for (int index = 0; index < inputStrings.Length; index++) {
                
                stringMatchers[index] = new RegexStringMatcher(inputStrings[index]
                    , regexes, doneEvent);
                ThreadPool.QueueUserWorkItem(new WaitCallback( (object arg) => {
                    RegexStringMatcher matcher = (RegexStringMatcher)arg;
                    matcher.exec();
                }), stringMatchers[index]);
            }
            doneEvent.WaitOne();
            List<string> result = new List<string>(Math.Max(10, inputStrings.Length * expectedPercentageOfMatchedStrings / 100));
            foreach (RegexStringMatcher matcher in stringMatchers) {
                if (matcher.IsMatch) {
                    result.Add(matcher.str);
                }
            }
            return result;
        }
    }
}
